/**
 * 用于接口请求，规范代码结构
 * 
 * @param api - 接口方法
 * @param options - 配置选项
 * @returns [apiFunc, loading, apiObj]
 * 
 * @example
 * const [apiFunc, loading, apiObj] = useApi(api, {
 *   isCancel: true,   // 连续请求，是否只响应最后一个请求结果
 *   isRepeat: true,   // 是否可重复请求/提交
 *   isSingle: false,  // 是否开启单例模式
 *   loading: {},      // 定义 loading 状态
 *   params: () => ({}), // 预设接口参数
 *   before: (params) => true, // 请求前处理
 *   outputModel: (params) => params, // 输出数据处理
 *   inputModel: (res) => res, // 输入数据处理
 *   success: (data, loadingKey, res) => {}, // 成功回调
 *   error: (err) => {}, // 失败回调
 *   loadingStart: (params, loadingKey) => {}, // loading开始回调
 *   loadingEnd: (loadingState) => {} // loading结束回调
 * });
 */
import useData from './useData'
import type { useApiFuncModel, useApiOptionsModel, useApiObjStatusModel } from './types'

export default function useApi<
  Q extends object,                 // 基础参数类型
  OQ extends object | void,        // outputModel 方法处理后参数类型
  R extends object | unknown | void, // 接口返回值类型
  RD extends object | void,        // inputModel 返回值处理后结果类型
  L extends object | undefined,    // loading 对象类型
  LK extends keyof L,              // loading 对象键名类型
  LS                               // loadingStart 方法返回值类型
>(
  api: (arg?: OQ, opt?: any) => Promise<R>,
  options?: useApiOptionsModel<R, RD, Q, OQ, L, LK, LS>
): [useApiFuncModel<R, RD, Q, LK>, L, useApiObjStatusModel<R, RD, LK, Q, OQ>] {
  const {
    isCancel,
    isRepeat,
    isSingle,
    loading,
    params,
    loadingStart,
    loadingEnd,
    before,
    success,
    error,
    inputModel,
    outputModel
  } = {
    isCancel: true,
    isRepeat: true,
    isSingle: false,
    ...options
  }

  const apiObj: useApiObjStatusModel<R, RD, LK, Q, OQ> = {
    status: 0,           // Api请求的状态，0 待请求, 1 请求中, 2 成功, 3 失败
    sendNum: {},         // 根据 loadingKey 来记录请求次数
    sendLatestKey: undefined, // 最新请求的 loadingKey
    pm: undefined,       // 最新一次请求结果
    err: '',             // 请求错误结果记录
    params: undefined,   // 请求参数记录
    output: undefined,   // 输出参数数据记录
    result: {
      res: undefined as R,    // 未处理前的数据
      data: undefined as RD,   // 经过 inputModel 处理后的结果
      loadingKey: '' as LK // loading键名
    },

    /**
     * 关闭正在请求的Api
     */
    cancelApi: async () => {
      const cancel = apiObj.pm?.cancel
      const pm = Promise.resolve()

      if (cancel) {
        cancel?.()
        apiObj.status = 0

        try {
          await apiObj.pm
        } catch (err) { }

        console.log('[cancel successful]')
      } else {
        console.log('[cancel error] find not cancel function.')
      }

      return pm
    }
  }

  /**
   * 调用API函数
   * @param query - 接口请求参数
   * @param loadingKey - 指定响应对应的loading键名
   */
  const apiFunc: useApiFuncModel<R, RD, Q, LK> = async (query, loadingKey = 'value' as LK) => {
    // 用来建立连接 loadingStart loadingEnd
    let loadingState = undefined as LS
    const sameSend: { [key in LK]?: number } = {}

    try {
      // 设置请求参数
      apiObj.params = params?.()
      apiObj.params && useData.mergeData(apiObj.params, query)

      // 请求前处理
      if (typeof before === 'function') {
        let bPm = before(apiObj.params && JSON.parse(JSON.stringify(apiObj.params)))

        if (bPm === false) {
          throw 'close send api.'
        }

        if (bPm instanceof Promise) {
          await bPm
        }
      }

      // 禁止重复请求处理
      if (!isRepeat && apiObj.status === 1) {
        return Promise.reject('操作频繁，请稍后再试')
      }

      // 是否单例模式
      if (isSingle && apiObj.status === 2) {
        success?.(apiObj.result.data, apiObj.result.loadingKey, apiObj.result.res)
        return Promise.resolve(apiObj.result)
      }

      // 输出给外部的参数数据
      apiObj.output = typeof outputModel === 'function'
        ? outputModel(apiObj.params && JSON.parse(JSON.stringify(apiObj.params)))
        : apiObj.params && JSON.parse(JSON.stringify(apiObj.params))

      // 连续请求模式，关闭上一次正在请求的此接口
      if (isCancel && isRepeat && apiObj.status === 1) {
        await apiObj.cancelApi()
      }

      // 取反 (相同 loadingKey 重复请求，上一个请求未完成)
      if (!(apiObj.sendLatestKey === loadingKey && apiObj.status === 1)) {
        // loading 处理
        if (loading && loadingKey && loadingKey in loading) {
          (loading as any)[loadingKey] = true
        }

        // loadingStart 回调
        if (loadingStart) {
          loadingStart(
            apiObj.params && JSON.parse(JSON.stringify(apiObj.params)),
            loadingKey
          )
        }
      }

      // 根据 loadingKey 分别记录请求次数
      if (loadingKey) {
        const k = loadingKey
        apiObj.sendNum[k] = (apiObj.sendNum[k] || 0) + 1
        sameSend[k] = apiObj.sendNum[k]
      }

      apiObj.sendLatestKey = loadingKey
      apiObj.status = 1

      // 调用 api
      apiObj.pm = api(apiObj.output)
      const res = await apiObj.pm

      apiObj.status = 2
      apiObj.result.res = res
      apiObj.result.loadingKey = loadingKey
      if (typeof inputModel === 'function') {
        apiObj.result.data = inputModel?.(res)
      }

      // 成功回调处理
      success?.(apiObj.result.data, loadingKey, apiObj.result.res)
    } catch (err: any) {
      // 失败处理
      apiObj.status = 3
      apiObj.err = err
      error?.(apiObj.err)
    }

    // 1. 最新请求的 loadingKey 与上一次不一样 || 2. 相同 loadingKey 的请求次数一致
    // 备注：条件1 不一致则直接处理loading关闭；条件2 请求次数一致则表示没有重复的请求
    const slk = apiObj.sendLatestKey

    if (apiObj.sendLatestKey != loadingKey || (slk && sameSend[slk] === apiObj.sendNum[slk])) {
      // loading 处理
      if (loading && loadingKey && loadingKey in loading) {
        (loading as any)[loadingKey] = false
      }

      // loadingEnd 处理
      loadingEnd?.(loadingState)
    }

    // 返回处理
    return new Promise((resolve, reject) => {
      if (apiObj.status === 2) {
        resolve(apiObj.result)
      } else if (apiObj.status === 3) {
        reject(apiObj.err)
      } else {
        reject(new Error('useApi exception'))
      }
    })
  }

  return [apiFunc, loading as L, { ...apiObj }]
}